// 阻塞队列的实现
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
using namespace std;

const int defaultcap = 5; // 默认容量大小
template <typename T>
class BlockQueue
{
private:
    bool IsFull()
    {
        return _q.size() >= _cap; // 只要满足这种情况的话就说明满了
    }
    bool IsEmpty()
    {
        return _q.empty();
    }

public:
    BlockQueue(int cap = defaultcap)
        : _cap(cap), _csleep_num(0), _psleep_num(0)
    {
        pthread_mutex_init(&_mutex, NULL);     // 对锁进行初始化
        pthread_cond_init(&_full, NULL);       // 对条件变量进行初始化
        pthread_cond_init(&_empty_cond, NULL); // 对条件变量进行初始化
    }
    void Equeue(const T &in)
    {
        // 我们在判断和push的时候都会触及到临界资源，所以需要加锁
        pthread_mutex_lock(&_mutex); // 加锁

        // 生产者调用
        while(IsFull()) // 判断下我们当前的阻塞队列是否是满的
        {

            //问题一：pthread_cond_wait是函数调用，返回值是int，有可能会失败的，所以我们需要判断一下
            //问题二：pthread_cond_wait可能会因为条件其实不满足，pthread_cond_wait被伪唤醒了，就是别人唤醒了，都填上了，就剩我自己了，就是误唤醒了
            //为了解决这个问题，我们直接一个循环，如果是上面的情况，只剩我一个人的情况，那么循环就会来一遍的，那么将我放回去的
            // 满了就进行等待了
            _psleep_num++; // 生产者休眠个数加1
            // 让生产线程等待。在条件变量中进行等待操作，等待是一定在临界区内进行休眠的
            std::cout << "生产者，进入休眠了: _psleep_num" <<  _psleep_num << std::endl;
            pthread_cond_wait(&_full, &_mutex); // 当前休眠的线程是持有锁的，
            
            // 重点一：我们要在挂起当前线程之前，我们要先自动释放锁给别人用
            // 重点二：当线程被唤醒的时候，默认就在临界区内唤醒，要从pthread_cond_wait()
            // 成功返回，需要当前线程，重新申请_mutex锁，然后继续往下执行
            // 重点三：如果我被唤醒了，但是申请锁失败了，我就会在锁上阻塞等待
            _psleep_num--; // 生产者休眠个数减1，因为我们醒过来了
        }    // 暂定了，没满的情况下，队列有空间
            //走到这里一定是有空间的，一但被伪唤醒的话，我们这里是会有二次检查的
        _q.push(in); // 将数据放入队列

       
        if (_csleep_num > 0) // 此时消费人数大于0的话
        {
            pthread_cond_signal(&_empty_cond); // 唤醒消费者消费了
            std::cout << "唤醒消费者..." << std::endl;

        }
            
        
        // 临时方案
        pthread_mutex_unlock(&_mutex); // 解锁
        //唤醒在解锁的前面和后面都是可以的，习惯的话我们可以将唤醒放到解锁的前面
        
    }
    T Pop()
    {

        // 消费者调用
        pthread_mutex_lock(&_mutex); // 加锁
        while(IsEmpty())                // 如果是空的话
        {
            _csleep_num++; // 消费者休眠个数加1

            // 空了就进行等待了，因为空了，所以不能进行消费，得去条件变量下进行等待操作了
            pthread_cond_wait(&_empty_cond, &_mutex); // 当前休眠的线程是持有锁的，
            // 重点一：我们要在挂起当前线程之前，我们要先自动释放锁给别人用
            // 重点二：当线程被唤醒的时候，默认就在临界区内唤醒，要从pthread_cond_wait()
            // 成功返回，需要当前线程，重新申请_mutex锁，然后继续往下执行
            // 重点三：如果我被唤醒了，但是申请锁失败了，我就会在锁上阻塞等待
            _csleep_num--; // 消费者休眠个数减1，因为我们醒过来了
        }

        T data = _q.front();           // 取出队列的第一个元素
        _q.pop();                      // 弹出队列的第一个元素

        //这里我们被消费者拿走了一个物品，说明商品不是满的，那么我们就得唤醒生产者进行生产操作了
        if(_psleep_num > 0) // 此时生产者休眠个数大于0的话
        {
            pthread_cond_signal(&_full_cond); // 唤醒生产者生产了
            std::cout << "唤醒消费者" << std::endl;
        }
            
        
        pthread_mutex_unlock(&_mutex); // 解锁
        return data;
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);     // 销毁锁
        pthread_cond_destroy(&_full);       // 销毁条件变量
        pthread_cond_destroy(&_empty_cond); // 销毁条件变量
    }

private:
    queue<T> _q; // queue里面放我们的数据类型，临界资源
    int _cap;    // 容量大小

    pthread_mutex_t _mutex;     // 互斥锁,互斥的关系是由锁维护的
    pthread_cond_t _full;       // 条件变量,用来通知生产者有数据可以消费,生产者如果生产满了的话，就将自己放到条件变量下去等待就行偶尔
    pthread_cond_t _empty_cond; // 条件变量,用来通知消费者有数据可以消费,消费者如果消费空了的话，就将自己放到条件变量下去等待就行偶尔
    int csleep_num;             // 消费者休眠个数
    int psleep_num;             // 生产者休眠个数
};
